package com.pai4j.pgc.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pai4j.domain.openapi.OpenApiPageQueryDTO;
import com.pai4j.domain.openapi.OpenApiUpsertDTO;
import com.pai4j.domain.vo.OpenApiDetailVO;
import com.pai4j.pgc.dao.OpenApiAuthParamRepository;
import com.pai4j.pgc.dao.OpenApiRepository;
import com.pai4j.pgc.dao.OpenApiRequestParamRepository;
import com.pai4j.pgc.dao.OpenApiResponseFieldRepository;
import com.pai4j.pgc.entity.OpenApiAuthParamEntity;
import com.pai4j.pgc.entity.OpenApiEntity;
import com.pai4j.pgc.entity.OpenApiRequestParamEntity;
import com.pai4j.pgc.entity.OpenApiResponseFieldEntity;
import jakarta.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Optional;

@Service
public class OpenApiService {

    @Autowired
    private OpenApiRepository openApiRepository;
    @Autowired
    private OpenApiAuthParamRepository authParamRepository;
    @Autowired
    private OpenApiRequestParamRepository requestParamRepository;
    @Autowired
    private OpenApiResponseFieldRepository responseFieldRepository;

    private final ObjectMapper mapper = new ObjectMapper();

    public Page<OpenApiEntity> page(OpenApiPageQueryDTO dto) {
        int pageNo = dto.getPageNo() == null || dto.getPageNo() < 1 ? 1 : dto.getPageNo();
        int pageSize = dto.getPageSize() == null || dto.getPageSize() < 1 ? 10 : dto.getPageSize();
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Direction.DESC, "updateDate", "id"));
        Specification<OpenApiEntity> spec = (root, query, cb) -> {
            var predicates = new ArrayList<jakarta.persistence.criteria.Predicate>();
            if (StringUtils.isNotBlank(dto.getApiId())) {
                predicates.add(cb.equal(root.get("apiId"), dto.getApiId()));
            }
            if (StringUtils.isNotBlank(dto.getName())) {
                predicates.add(cb.like(root.get("name"), "%" + dto.getName().trim() + "%"));
            }
            if (dto.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), dto.getStatus()));
            }
            if (StringUtils.isNotBlank(dto.getHttpMethod())) {
                predicates.add(cb.equal(root.get("httpMethod"), dto.getHttpMethod()));
            }
            if (StringUtils.isNotBlank(dto.getVersion())) {
                predicates.add(cb.equal(root.get("version"), dto.getVersion()));
            }
            return cb.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
        };
        return openApiRepository.findAll(spec, pageable);
    }

    public OpenApiDetailVO getDetail(Long id) {
        OpenApiEntity e = openApiRepository.findById(id).orElse(null);
        if (e == null) return null;
        return assembleDetail(e);
    }

    private OpenApiDetailVO assembleDetail(OpenApiEntity e) {
        OpenApiDetailVO vo = new OpenApiDetailVO();
        vo.setId(e.getId());
        vo.setApiId(e.getApiId());
        vo.setName(e.getName());
        vo.setDescription(e.getDescription());
        vo.setDefinition(e.getDefinition());
        vo.setJsonpDefinition(e.getJsonpDefinition());
        vo.setHttpMethod(e.getHttpMethod());
        vo.setRequestExample(e.getRequestExample());
        vo.setResponseExample(e.getResponseExample());
        vo.setVersion(e.getVersion());
        vo.setStatus(e.getStatus());
        vo.setRemark(e.getRemark());

        // children
        var auths = authParamRepository.findByOpenapiIdOrderBySortNoAscIdAsc(e.getId());
        var reqs = requestParamRepository.findByOpenapiIdOrderBySortNoAscIdAsc(e.getId());
        var fields = responseFieldRepository.findByOpenapiIdOrderBySortNoAscIdAsc(e.getId());

        var authVOs = new ArrayList<OpenApiDetailVO.AuthParamVO>();
        for (OpenApiAuthParamEntity a : auths) {
            var v = new OpenApiDetailVO.AuthParamVO();
            v.setId(a.getId());
            v.setParamKey(a.getParamKey());
            v.setParamName(a.getParamName());
            v.setParamType(a.getParamType());
            v.setRequired(a.getRequired());
            v.setFieldDesc(a.getFieldDesc());
            v.setExample(a.getExample());
            v.setSortNo(a.getSortNo());
            authVOs.add(v);
        }
        vo.setAuthParams(authVOs);

        var reqVOs = new ArrayList<OpenApiDetailVO.RequestParamVO>();
        for (OpenApiRequestParamEntity rp : reqs) {
            var v = new OpenApiDetailVO.RequestParamVO();
            v.setId(rp.getId());
            v.setParamName(rp.getParamName());
            v.setParamType(rp.getParamType());
            v.setRequired(rp.getRequired());
            v.setFieldKey(rp.getFieldKey());
            v.setDescription(rp.getDescription());
            v.setExample(rp.getExample());
            v.setDefaultValue(rp.getDefaultValue());
            v.setSortNo(rp.getSortNo());
            reqVOs.add(v);
        }
        vo.setRequestParams(reqVOs);

        var fieldVOs = new ArrayList<OpenApiDetailVO.ResponseFieldVO>();
        for (OpenApiResponseFieldEntity rf : fields) {
            var v = new OpenApiDetailVO.ResponseFieldVO();
            v.setId(rf.getId());
            v.setFieldName(rf.getFieldName());
            v.setFieldType(rf.getFieldType());
            v.setFieldDesc(rf.getFieldDesc());
            v.setExample(rf.getExample());
            v.setParentField(rf.getParentField());
            v.setSortNo(rf.getSortNo());
            fieldVOs.add(v);
        }
        vo.setResponseFields(fieldVOs);
        return vo;
    }

    @Transactional
    public OpenApiDetailVO upsert(OpenApiUpsertDTO dto, String operator) {
        OpenApiEntity e;
        LocalDateTime now = LocalDateTime.now();
        if (dto.getId() != null) {
            e = openApiRepository.findById(dto.getId()).orElse(new OpenApiEntity());
        } else {
            // deduplicate by apiId if provided
            if (StringUtils.isNotBlank(dto.getApiId())) {
                Optional<OpenApiEntity> ex = openApiRepository.findByApiId(dto.getApiId());
                e = ex.orElse(new OpenApiEntity());
            } else {
                e = new OpenApiEntity();
            }
            e.setCreateDate(now);
            e.setCreateBy(operator);
        }
        // fill fields
        e.setApiId(dto.getApiId());
        e.setName(dto.getName());
        e.setDescription(dto.getDescription());
        e.setDefinition(dto.getDefinition());
        e.setJsonpDefinition(dto.getJsonpDefinition());
        e.setHttpMethod(dto.getHttpMethod());
        e.setRequestExample(dto.getRequestExample());
        e.setResponseExample(dto.getResponseExample());
        e.setVersion(StringUtils.isNotBlank(dto.getVersion()) ? dto.getVersion() : "v1");
        e.setStatus(dto.getStatus() == null ? 1 : dto.getStatus());
        e.setRemark(dto.getRemark());
        e.setUpdateDate(now);
        e.setUpdateBy(operator);
        e = openApiRepository.save(e);

        // children simple strategy: delete then recreate
        Long oid = e.getId();
        authParamRepository.deleteByOpenapiId(oid);
        requestParamRepository.deleteByOpenapiId(oid);
        responseFieldRepository.deleteByOpenapiId(oid);

        if (dto.getAuthParams() != null) {
            for (var a : dto.getAuthParams()) {
                OpenApiAuthParamEntity en = new OpenApiAuthParamEntity();
                en.setOpenapiId(oid);
                en.setParamKey(a.getParamKey());
                en.setParamName(a.getParamName());
                en.setParamType(StringUtils.isNotBlank(a.getParamType()) ? a.getParamType() : "string");
                en.setRequired(a.getRequired() == null ? 1 : a.getRequired());
                en.setFieldDesc(a.getFieldDesc());
                en.setExample(a.getExample());
                en.setSortNo(a.getSortNo() == null ? 0 : a.getSortNo());
                en.setCreateDate(now);
                en.setUpdateDate(now);
                authParamRepository.save(en);
            }
        }
        if (dto.getRequestParams() != null) {
            for (var r : dto.getRequestParams()) {
                OpenApiRequestParamEntity en = new OpenApiRequestParamEntity();
                en.setOpenapiId(oid);
                en.setParamName(r.getParamName());
                en.setParamType(r.getParamType());
                en.setRequired(r.getRequired() == null ? 0 : r.getRequired());
                en.setFieldKey(r.getFieldKey());
                en.setDescription(r.getDescription());
                en.setExample(r.getExample());
                en.setDefaultValue(r.getDefaultValue());
                en.setSortNo(r.getSortNo() == null ? 0 : r.getSortNo());
                en.setCreateDate(now);
                en.setUpdateDate(now);
                requestParamRepository.save(en);
            }
        }
        if (dto.getResponseFields() != null) {
            for (var f : dto.getResponseFields()) {
                OpenApiResponseFieldEntity en = new OpenApiResponseFieldEntity();
                en.setOpenapiId(oid);
                en.setFieldName(f.getFieldName());
                en.setFieldType(f.getFieldType());
                en.setFieldDesc(f.getFieldDesc());
                en.setExample(f.getExample());
                en.setParentField(f.getParentField());
                en.setSortNo(f.getSortNo() == null ? 0 : f.getSortNo());
                en.setCreateDate(now);
                en.setUpdateDate(now);
                responseFieldRepository.save(en);
            }
        }
        return assembleDetail(e);
    }

    @Transactional
    public void delete(Long id) {
        authParamRepository.deleteByOpenapiId(id);
        requestParamRepository.deleteByOpenapiId(id);
        responseFieldRepository.deleteByOpenapiId(id);
        openApiRepository.deleteById(id);
    }

    public String exportAsJson(Long id) throws Exception {
        OpenApiDetailVO vo = getDetail(id);
        if (vo == null) return null;
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vo);
    }

    public String exportAsMarkdown(Long id) {
        OpenApiDetailVO vo = getDetail(id);
        if (vo == null) return null;
        StringBuilder sb = new StringBuilder();
        sb.append("# ").append(vo.getName()).append(" ("+vo.getApiId()+")\n\n");
        sb.append("- 方法: ").append(vo.getHttpMethod()).append("\n");
        sb.append("- 版本: ").append(vo.getVersion()).append("\n");
        sb.append("- 状态: ").append(vo.getStatus() != null && vo.getStatus() == 1 ? "启用" : "停用").append("\n\n");
        if (StringBuilder.class != null && vo.getDescription() != null) {
            sb.append("## 描述\n").append(vo.getDescription()).append("\n\n");
        }
        if (vo.getDefinition() != null) {
            sb.append("## 接口定义\n").append("\n````\n").append(vo.getDefinition()).append("\n````\n\n");
        }
        if (vo.getRequestExample() != null) {
            sb.append("## 请求示例\n").append("\n````\n").append(vo.getRequestExample()).append("\n````\n\n");
        }
        if (vo.getResponseExample() != null) {
            sb.append("## 响应示例\n").append("\n````json\n").append(vo.getResponseExample()).append("\n````\n\n");
        }
        if (vo.getAuthParams() != null && !vo.getAuthParams().isEmpty()) {
            sb.append("## 认证参数\n\n");
            sb.append("| 键(Key) | 名称 | 类型 | 必传 | 字段说明 | 示例 | 排序 |\n");
            sb.append("|---|---|---|---|---|---|---|\n");
            for (var a : vo.getAuthParams()) {
                sb.append("|")
                        .append(a.getParamKey() == null ? "" : a.getParamKey()).append("|")
                        .append(a.getParamName() == null ? "" : a.getParamName()).append("|")
                        .append(a.getParamType() == null ? "" : a.getParamType()).append("|")
                        .append(a.getRequired() != null && a.getRequired() == 1 ? "是" : "否").append("|")
                        .append(a.getFieldDesc() == null ? "" : a.getFieldDesc()).append("|")
                        .append(a.getExample() == null ? "" : a.getExample()).append("|")
                        .append(a.getSortNo() == null ? 0 : a.getSortNo()).append("|\n");
            }
            sb.append("\n");
        }
        if (vo.getRequestParams() != null && !vo.getRequestParams().isEmpty()) {
            sb.append("## 请求参数\n\n");
            sb.append("| 参数名 | 类型 | 必传 | 键名(Key) | 说明 | 示例 | 默认值 | 排序 |\n");
            sb.append("|---|---|---|---|---|---|---|---|\n");
            for (var r : vo.getRequestParams()) {
                sb.append("|")
                        .append(r.getParamName() == null ? "" : r.getParamName()).append("|")
                        .append(r.getParamType() == null ? "" : r.getParamType()).append("|")
                        .append(r.getRequired() != null && r.getRequired() == 1 ? "是" : "否").append("|")
                        .append(r.getFieldKey() == null ? "" : r.getFieldKey()).append("|")
                        .append(r.getDescription() == null ? "" : r.getDescription()).append("|")
                        .append(r.getExample() == null ? "" : r.getExample()).append("|")
                        .append(r.getDefaultValue() == null ? "" : r.getDefaultValue()).append("|")
                        .append(r.getSortNo() == null ? 0 : r.getSortNo()).append("|\n");
            }
            sb.append("\n");
        }
        if (vo.getResponseFields() != null && !vo.getResponseFields().isEmpty()) {
            sb.append("## 响应 data 字段\n\n");
            sb.append("| 字段名 | 类型 | 父字段 | 说明 | 示例 | 排序 |\n");
            sb.append("|---|---|---|---|---|---|\n");
            for (var f : vo.getResponseFields()) {
                sb.append("|")
                        .append(f.getFieldName() == null ? "" : f.getFieldName()).append("|")
                        .append(f.getFieldType() == null ? "" : f.getFieldType()).append("|")
                        .append(f.getParentField() == null ? "" : f.getParentField()).append("|")
                        .append(f.getFieldDesc() == null ? "" : f.getFieldDesc()).append("|")
                        .append(f.getExample() == null ? "" : f.getExample()).append("|")
                        .append(f.getSortNo() == null ? 0 : f.getSortNo()).append("|\n");
            }
            sb.append("\n");
        }
        return sb.toString();
    }
}
